# -*-coding:utf-8-*-
import random


class Card(object):
    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r},rank={rank!r})". \
            format(___class__=self.__class__, **self.__dict__)

    def __str__(self):
        return "{rank}{suit.symbol}".format(**self.__dict__)

    def __format__(self, format_spec):
        if format_spec == "":
            return str(self)
        rs = format_spec.replace("%r", self.rank).replace("%s", self.suit.name)
        rs = rs.replace("%%", "%")
        return rs

    def __bytes__(self):
        class_code = self.__class__.__name__[0]
        rank_number_str = {'A': '1', 'J': '11', 'Q': '12', 'K': '13'}.get(self.rank, self.rank)
        string_ = "(" + " ".join([class_code, rank_number_str, self.suit.name, ]) + ")"
        return bytes(string_, encoding='utf8')


class NumberCard(Card):
    def __init__(self, rank, suit):
        super(NumberCard, self).__init__(str(rank), suit, rank, rank)


class AceCard(Card):
    def __init__(self, rank, suit):
        super(AceCard, self).__init__("A", suit, 1, 11)


class FaceCard(Card):
    def __init__(self, rank, suit):
        super(FaceCard, self).__init__({11: 'J', 12: 'Q', 13: 'K'}[rank], suit, 10, 10)


class Suit:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol


class Hand:

    def __str__(self):
        return ", ".join(map(str, self.card))

    def __repr__(self):
        return "{__class__.__name__}({dealer_card!r}, {_cards_str})". \
            format(__class__=self.__class__, cards_str=", ".join(map(repr, self.card)), **self.__dict__)


class Hand_Lazy(Hand):
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self._cards = list(cards)

    @property
    def total(self):
        delta_soft = max(c.soft - c.hard for c in self._cards)
        hard_total = sum(c.hard for c in self._cards)
        if hard_total + delta_soft <= 21: return hard_total + delta_soft
        return hard_total

    @property
    def card(self):
        return self._cards

    @card.setter
    def card(self, aCard):
        self._cards.append(aCard)

    @card.deleter
    def card(self):
        self._cards.pop(-1)

    def split(self, deck):
        assert self._cards[0].rank == self._cards[1].rank
        c1 = self._cards[-1]
        del self.card
        self.card = deck.pop()
        h_new = self.__class__(self.dealer_card, c1, deck.pop())
        return h_new


class Hand_Eager(Hand):
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.total = 0
        self._delta_soft = 0
        self._hard_total = 0
        self._cards = list()
        for c in cards:
            self.card = c

    @property
    def card(self):
        return self._cards

    @card.setter
    def card(self, aCard):
        self._cards.append(aCard)
        self._delta_soft = max(aCard.soft - aCard.hard,
                               self._delta_soft)
        self._hard_total += aCard.hard
        self._set_total()

    @card.deleter
    def card(self):
        removed = self._cards.pop(-1)
        self._delta_soft = max(c.soft - c.hard for c in self._cards)
        self._set_total()

    def _set_total(self):
        if self._hard_total + self._delta_soft <= 21:
            self.total = self._hard_total + self._delta_soft
        else:
            self.total = self._hard_total


def card10(rank, suit):
    if rank == 1:
        return AceCard(rank, suit)
    elif 2 <= rank < 11:
        return NumberCard(rank, suit)
    elif 11 <= rank < 14:
        return FaceCard(rank, suit)
    else:
        raise Exception("Rank out of range")


class Deck:
    def __init__(self):
        self._cards = [card10(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]
        random.shuffle(self._cards)

    def pop(self):
        return self._cards.pop()


class BlackJackCard:
    __slots__ = ('rank', 'suit', 'hard', 'soft')

    def __init__(self, rank, suit, hard, soft):
        super().__setattr__('rank', rank)
        super().__setattr__('suit', suit)
        super().__setattr__('hard', hard)
        super().__setattr__('soft', soft)

    def __str__(self):
        return "{0.rank}{0.suit}".format(self)

    def __setattr__(self, name, value):
        raise AttributeError(
            "'{__class__.__name__}'has no attribute '{name}'".format(___class__=self.__class__, name=name))


class BlackJackCard2(tuple):
    def __new__(cls, rank, suit, hard, soft):
        return super().__new__(cls, (rank, suit, hard, soft))

    def __getattr__(self, name):
        return self[{'rank': 0, 'suit': 1, 'hard': 2, 'soft': 3}[name]]

    def __setattr__(self, name, value):
        raise AttributeError


class RateTimeDistance(dict):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)

    def __getattr__(self, name):
        return self.get(name, None)

    def __setattr__(self, name, value):
        self[name] = value
        self._solve()

    def __dir__(self):
        return list(self.keys())

    def _solve(self):
        if self.rate is not None and self.time is not None:
            self['distance'] = self.rate * self.time
        elif self.rate is not None and self.distance is not None:
            self['time'] = self.distance / self.rate
        elif self.time is not None and self.distance is not None:
            self['rate'] = self.distance / self.time


class BlackJackCard3:
    def __init__(self, rank, suit, hard, soft):
        super().__setattr__('rank', rank)
        super().__setattr__('suit', suit)
        super().__setattr__('hard', hard)
        super().__setattr__('soft', soft)

    def __setattr__(self, name, value):
        if name in self.__dict__:
            raise AttributeError("Cannot set {name}".format(name=name))
        raise AttributeError(
            "'{__class__.__name__}' has no attribute'{name}'".format(___class__=self.__class__, name=name))

    def __getattribute__(self, name):
        if name.startswith('_'): raise AttributeError
        return object.__getattribute__(self, name)


class UnitValue_1:
    def __init__(self, unit):
        self.value = None
        self.unit = unit
        self.default_format = "5.2f"

    def __set__(self, instance, value):
        self.value = value

    def __str__(self):
        return "{value:{spec}} {unit}".format(spec=self.default_format, **self.__dict__)

    def __format__(self, spec="5.2f"):
        return "{value:{spec}} {unit}".format(spec=spec, **self.__dict__)


class RTD_1:
    rate = UnitValue_1("kt")
    time = UnitValue_1("hr")
    distance = UnitValue_1("nm")

    def __init__(self, rate=None, time=None, distance=None):
        if rate is None:
            self.time = time
            self.distance = distance
            self.rate = distance / time
        if time is None:
            self.rate = rate
            self.distance = distance
            self.time = distance / rate
        if distance is None:
            self.rate = rate
            self.time = time
            self.distance = rate * time

    def __str__(self):
        return "rate:{0.rate} time:{0.time} distance:{0.distance}".format(self)


class Unit:
    conversion = 1.0

    def __get__(self, instance, owner):
        return instance.kph * self.conversion

    def __set__(self, instance, value):
        instance.kph = value / self.conversion


class Knots(Unit):
    conversion = 0.5399568


class MPH(Unit):
    conversion = 0.61237119


class KPH(Unit):
    def __get__(self, instance, owner):
        return instance._kph

    def __set__(self, instance, value):
        instance._kph = value


class Measurement:
    kph = KPH()
    knots = Knots()
    mph = MPH()

    def __init__(self, kph=None, mph=None, knots=None):
        if kph:
            self.kph = kph
        elif mph:
            self.mph = mph
        elif knots:
            self.knots = knots
        else:
            raise TypeError

    def __str__(self):
        return "rate: {0.kph} kph={0.mph} mph={0.knots} knots".format(self)


if __name__ == '__main__':
    m1 = RTD_1(rate=5.8, distance=12)
    print(str(m1))
